﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading.Tasks;

namespace Tnelab.MBWrapper
{
    class WkeWebView : IWebView
    {
        static readonly object web_view_dic_lock=new object();
        static readonly Dictionary<IntPtr, WkeWebView> web_view_dic = new Dictionary<IntPtr, WkeWebView>();
        static MBWke.wkeJsNativeFunction JsNativeFunction;
        static long call_id_seed = 0;
        static readonly object call_id_seed_lock = new object();
        static readonly object js_call_native_event_arg_map_lock = new object();
        static readonly Dictionary<long, JsCallNativeEventArg> js_call_native_event_arg_map = new Dictionary<long, JsCallNativeEventArg>();        
        static WkeWebView()
        {
            JsNativeFunction = (es, p) =>
            {
                JsCallNativeEventArg args = new JsCallNativeEventArg();
                args.MessageId = MBWke.jsToInt(es, MBWke.jsArg(es, 0));
                args.Request = Marshal.PtrToStringUni(MBWke.jsToStringW(es, MBWke.jsArg(es, 1)));
                args.CallId = call_id_seed;
                var func = MBWke.jsArg(es, 2);
                string funcName;
                lock (call_id_seed_lock)
                {
                    funcName = $"MbQueryCallback_{call_id_seed}";
                    call_id_seed++;
                }
                args.Func = funcName;
                MBWke.jsSetGlobal(es, funcName, func);
                WkeWebView webView;
                lock (web_view_dic)
                {
                    webView = web_view_dic[MBWke.jsGetWebView(es)];
                }
                args.WebView = webView.WebView;
                webView.OnJsCallNative(args);
                return MBWke.jsUndefined();
            };
            MBWke.wkeJsBindFunction("mbQuery", JsNativeFunction, IntPtr.Zero, 1);
        }
        protected IntPtr WebView { get; set; }
        protected IntPtr ParentHandle { get; set; }
        public Action<Action> UIInvoke { get; protected set; }

        public bool IsVip { get; private set; } = false;
        /// <summary>
        /// 获取webview大小
        /// </summary>
        public Size Size { get => new Size(MBWke.wkeGetWidth(WebView),MBWke.wkeGetHeight(WebView));}
        /// <summary>
        /// webview是否透明
        /// </summary>
        public bool Transparent
        {
            get
            {
                return MBWke.wkeIsTransparent(WebView);
            }
            set
            {
                MBWke.wkeSetTransparent(WebView, value);
            }
        }
        public bool LoadUrlEndEnable { get; set; } = true;

        public event EventHandler<LoadUrlBeginArgs> LoadUrlBegin;
        public event EventHandler<LoadUrlEndArgs> LoadUrlEnd;
        public event EventHandler<PaintBitUpdatedEventArg> PaintBitUpdated;
        public event EventHandler<JsCallNativeEventArg> JsCallNative;
        #region 私有方法
        MBWke.wkePaintBitUpdatedCallback PaintBitUpdatedCallback;
        unsafe void WkeOnPaintBitUpdated()
        {
            if (PaintBitUpdatedCallback == null)
            {
                PaintBitUpdatedCallback = (IntPtr webView, IntPtr param, IntPtr buffer, MBWke.wkeRect* r, int width, int height) =>
                {
                    if (this.PaintBitUpdated != null)
                    {
                        var arg = new PaintBitUpdatedEventArg();
                        arg.WebView = this;
                        arg.Width = width;
                        arg.Height = height;
                        //arg.Param = param;
                        arg.Buffer = new byte[4 * width * height];
                        Marshal.Copy(buffer, arg.Buffer, 0, arg.Buffer.Length);
                        arg.Rect = new Rect();
                        arg.Rect.X = r->x;
                        arg.Rect.Y = r->y;
                        arg.Rect.Width = r->w;
                        arg.Rect.Height = r->h;
                        this.PaintBitUpdated(this, arg);
                    }
                };
            }
            MBWke.wkeOnPaintBitUpdated(WebView, PaintBitUpdatedCallback, IntPtr.Zero);
        }
        MBWke.wkeLoadUrlBeginCallback LoadUrlBeginCallback;
        void WkeOnLoadUrlBegin()
        {
            if (LoadUrlBeginCallback == null)
            {
                LoadUrlBeginCallback = (webView, param, url, job) => {
                    if (this.LoadUrlBegin != null)
                    {
                        var args = new LoadUrlBeginArgs();
                        args.WebView = this;
                        args.Url = new Uri(url);
                        args.Datas = new List<byte>();
                        this.LoadUrlBegin(this, args);
                        if (args.Datas.Count > 0)
                        {
                            var dataPtr = Marshal.AllocHGlobal(args.Datas.Count);
                            var datas = args.Datas.ToArray();
                            Marshal.Copy(datas, 0, dataPtr, args.Datas.Count);
                            MBWke.wkeNetSetData(job, dataPtr, args.Datas.Count);
                            Marshal.FreeHGlobal(dataPtr);
                        }
                        else
                        {
                            if (this.LoadUrlEndEnable)
                            {
                                MBWke.wkeNetHookRequest(job);
                            }
                        }
                        return true;
                    }
                    return false;
                };
            }
            MBWke.wkeOnLoadUrlBegin(WebView, LoadUrlBeginCallback, IntPtr.Zero);
        }
        MBWke.wkeLoadUrlEndCallback LoadUrlEndCallback;
        void WkeOnLoadUrlEnd()
        {
            if (LoadUrlEndCallback == null)
            {
                LoadUrlEndCallback = (webView, param, url, job, buf, len) =>
                {
                    if (this.LoadUrlEnd != null)
                    {
                        var args = new LoadUrlEndArgs();
                        args.WebView = this;
                        args.Url = new Uri(url);

                        var datas = new byte[len];
                        if (buf != IntPtr.Zero)
                            Marshal.Copy(buf, datas, 0, datas.Length);
                        args.Datas = datas;
                        args.NeedUpdate = false;
                        this.LoadUrlEnd(this, args);
                        if (args.NeedUpdate)
                        {
                            var dataPtr = Marshal.AllocCoTaskMem(args.Datas.Count);
                            Marshal.Copy((byte[])args.Datas, 0, dataPtr, args.Datas.Count);
                            MBWke.wkeNetSetData(job, dataPtr, args.Datas.Count);
                            Marshal.FreeCoTaskMem(dataPtr);
                        }
                    }
                };
            }
            MBWke.wkeOnLoadUrlEnd(WebView, LoadUrlEndCallback, IntPtr.Zero);
        }
        #endregion
        /// <summary>
        /// 构造WebView
        /// </summary>
        public WkeWebView(IntPtr parentHandle, Action<Action> uiInvoke)
        {
            this.UIInvoke = uiInvoke;
            ParentHandle = parentHandle;            
            WebView = MBWke.wkeCreateWebView();
            lock(web_view_dic_lock)
            {
                web_view_dic.Add(WebView, this);
            }
            MBWke.wkeSetHandle(WebView,ParentHandle);
            WkeOnPaintBitUpdated();
            WkeOnLoadUrlBegin();
            WkeOnLoadUrlEnd();
            //MBWke.wkeSetDebugConfig(this.WebView,"showDevTools", @"C:\Users\MastZ\Desktop\miniblink-20200824\front_end\inspector.html");
        }
        protected void OnJsCallNative(JsCallNativeEventArg args)
        {
            if (this.JsCallNative != null)
            {
                Task.Run(() => { 
                    lock (js_call_native_event_arg_map_lock) {
                        js_call_native_event_arg_map.Add(args.CallId, args);
                    }
                    this.JsCallNative(this,args);
                });
            }

        }
        public void Resize(int width,int height)
        {
            MBWke.wkeResize(WebView, width, height);
        }
        /// <summary>
        /// 加载URL
        /// </summary>
        /// <param name="url"></param>
        public void LoadUrl(Uri url)
        {
            MBWke.wkeLoadURLW(WebView, url.ToString());
        }
        /// <summary>
        /// 触发键盘按下消息
        /// </summary>
        /// <param name="virtualKeyCode">虚拟键值</param>
        /// <param name="flags"></param>
        /// <param name="systemKey">是否系统键</param>
        /// <returns></returns>
        public bool FireKeyDownEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyDownEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyUpEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyUpEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public void SetFocus()
        {
            MBWke.wkeSetFocus(WebView);
        }

        public void KillFocus()
        {
            MBWke.wkeKillFocus(WebView);
        }

        public bool FireKeyPressEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyPressEvent(WebView, virtualKeyCode, flags, systemKey);
        }

        public bool FireMouseWheelEvent(int x, int y, int delta, uint flags)
        {
            return MBWke.wkeFireMouseWheelEvent(WebView, x, y, delta, flags);
        }

        public bool FireMouseEvent(uint message, int x, int y, uint flags)
        {
            return MBWke.wkeFireMouseEvent(WebView, message, x, y, flags);
        }

        public CursorType GetCursorType()
        {
            return (CursorType)MBWke.wkeGetCursorInfoType(WebView);
        }
        string RunJs(string script)
        {
            var jv = MBWke.wkeRunJSW(WebView, script);
            var es = MBWke.wkeGlobalExec(WebView);
            var val = MBWke.jsToStringW(es, jv);
            var r = Marshal.PtrToStringUni(val);
            return r;
        }
        public Task<string> RunJsAsync(string script)
        {
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
            Task.Run(() => {
                this.UIInvoke(() =>
                {
                    var r=this.RunJs(script);
                    tcs.SetResult(r);
                });
            });
            return tcs.Task;
        }
        public void Dispose()
        {            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        bool is_disposed = false;
        protected void Dispose(bool disposing)
        {
            if (this.WebView == IntPtr.Zero)
                throw new Exception("资源释放异常WebView不能为0");
            if (!is_disposed)
            {
                if (disposing)
                {
                    //Clean Up managed resources  
                }
                lock (web_view_dic_lock)
                {
                    web_view_dic.Remove(WebView);
                }
                lock (js_call_native_event_arg_map_lock)
                {
                    js_call_native_event_arg_map.Values.Where(it => it.WebView == this.WebView)
                        .ToList().ForEach(it=> js_call_native_event_arg_map.Remove(it.CallId));                    
                }
                MBWke.wkeDestroyWebView(this.WebView);
            }
            is_disposed = true;
        }

        public void ResponseJsCallNative(long callId, int messageId, string response)
        {
            this.UIInvoke(() =>
            {
                JsCallNativeEventArg args;
                lock (js_call_native_event_arg_map_lock)
                {
                    args = js_call_native_event_arg_map[callId];
                    js_call_native_event_arg_map.Remove(callId);
                }
                var es = MBWke.wkeGlobalExec(args.WebView);
                var func = MBWke.jsGetGlobal(es, args.Func);
                MBWke.jsCall(es, func, MBWke.jsGetGlobal(es, "window"), new Int64[] { MBWke.jsInt(args.MessageId), MBWke.jsStringW(es, response) }, 2);
                MBWke.jsDeleteObjectProp(es, MBWke.jsGetGlobal(es, "window"), args.Func);
            });
        }
        void IWebView.JsExecStateInvoke(Action<IntPtr> action)
        {
            throw new NotImplementedException();
        }

        ~WkeWebView()
        {
            Dispose(false);
        }
    }
}
